var BigInt = (module.exports = function(n, v) {
  if (BigInt.isBigInt(n)) {
    this.buffer = Buffer.allocUnsafe(n.buffer.length);
    n.buffer.copy(this.buffer, 0);
    this.sign = n.sign;
  } else {
    this.buffer = Buffer.alloc(n);
    this.sign = 1;

    v = v || 0;

    if (v !== 0) {
      if (v < 0) {
        this.sign = -1;
        v = -v;
      }

      v = v.toString(16);

      var size = Math.ceil(v.length / 2);
      var carry = size * 2 - v.length;

      for (var i = 0; i < size; i++) {
        var start = (size - i - 1) * 2 - carry;
        this.buffer.writeUInt8(
          parseInt(start === -1 ? v.substr(0, 1) : v.substr(start, 2), 16),
          i
        );
      }
    }
  }
});

/*
 * STATICS
 */
BigInt.isBigInt = function(v) {
  return v && v.buffer && Buffer.isBuffer(v.buffer);
};

BigInt.toBigInt = function(n, v) {
  return new BigInt(n, v);
};

BigInt.fromBuffer = function(b, sign) {
  sign = typeof sign === 'undefined' ? 1 : sign;
  var bi = new BigInt(0);
  bi.sign = sign;
  bi.buffer = b;
  return bi;
};

/*
 * ADD / SUB
 */
BigInt.prototype.add = function(v) {
  if (!BigInt.isBigInt(v)) {
    v = BigInt.toBigInt(this.buffer.length, v);
  }

  if (this.sign !== v.sign) {
    return this.neg().sub(v);
  }

  var carry = 0;
  var n = Math.max(v.buffer.length, this.buffer.length);
  var result = new BigInt(n);
  for (var i = 0; i < n; i++) {
    var r =
      (i < this.buffer.length ? this.buffer.readUInt8(i) : 0) +
      (i < v.buffer.length ? v.buffer.readUInt8(i) : 0) +
      carry;
    result.buffer.writeUInt8(r & 0xff, i);
    carry = r >> 8;
  }

  result.sign = this.sign;

  return result;
};

BigInt.prototype.neg = function() {
  var result = new BigInt(this);
  result.sign *= -1;
  return result;
};

BigInt.prototype.abs = function() {
  var result = new BigInt(this);
  result.sign = 1;
  return result;
};

BigInt.prototype.sub = function(v) {
  if (!BigInt.isBigInt(v)) {
    v = BigInt.toBigInt(this.buffer.length, v);
  }

  if (this.sign !== v.sign) {
    return this.add(v.neg());
  }

  var carry = 0;
  var a = new BigInt(this);
  var b = new BigInt(v);
  var n = Math.max(a.buffer.length, b.buffer.length);
  var result = new BigInt(n);
  var sign = this.sign;

  if (a.abs().lt(b.abs())) {
    var t = a;
    a = b;
    b = t;
    sign *= -1;
  }

  for (var i = 0; i < n; i++) {
    var va = i < a.buffer.length ? a.buffer.readUInt8(i) : 0;
    var vb = i < b.buffer.length ? b.buffer.readUInt8(i) : 0;
    var c = 0;
    var r = va - vb - carry;

    while (r < 0) {
      r += 0xff;
      c--;
    }

    result.buffer.writeUInt8(r & 0xff, i);
    carry = (r >> 8) + c;
  }

  result.sign = sign;

  return result;
};

/*
 * EXPORTS
 */
BigInt.prototype.toBuffer = function() {
  return this.buffer;
};

BigInt.prototype.toNumber = function() {
  var b = Buffer.allocUnsafe(this.buffer.length);

  for (var i = 0; i < this.buffer.length; i++) {
    b.writeUInt8(this.buffer.readUInt8(this.buffer.length - i - 1), i);
  }

  return parseInt(b.toString('hex'), 16);
};

/*
 * COMPARE
 */
BigInt.prototype.compare = function(v) {
  if (!BigInt.isBigInt(v)) {
    v = BigInt.toBigInt(this.buffer.length, v);
  }

  var n = Math.max(v.buffer.length, this.buffer.length);

  if (this.sign > v.sign) return 1;
  if (this.sign < v.sign) return -1;

  for (var i = n - 1; i >= 0; i--) {
    var a = i < this.buffer.length ? this.buffer.readUInt8(i) : 0;
    var b = i < v.buffer.length ? v.buffer.readUInt8(i) : 0;
    if (a !== b) {
      return a > b ? this.sign : -this.sign;
    }
  }

  return 0;
};

BigInt.prototype.lt = function(v) {
  return this.compare(v) < 0;
};

BigInt.prototype.le = function(v) {
  return this.compare(v) <= 0;
};

BigInt.prototype.gt = function(v) {
  return this.compare(v) > 0;
};

BigInt.prototype.ge = function(v) {
  return this.compare(v) >= 0;
};
